<template>
	<div
		:class="{
			'tui-chat': true,
			'tui-chat-h5': isMobile
		}"
		@click="onMessageListBackgroundClick"
	>
		<!-- <JoinGroupCard /> -->
		<div class="tui-chat-main">
			<div v-if="isOfficial" class="tui-chat-safe-tips">
				<span>
					{{
						TUITranslateService.t(
							'TUIChat.【安全提示】本 APP 仅用于体验腾讯云即时通信 IM 产品功能，不可用于业务洽谈与拓展。请勿轻信汇款、中奖等涉及钱款的信息，勿轻易拨打陌生电话，谨防上当受骗。'
						)
					}}
				</span>
				<a @click="openComplaintLink(Link.complaint)">{{ TUITranslateService.t('TUIChat.点此投诉') }}</a>
			</div>
			<MessageGroupApplication v-if="isGroup" :key="props.groupID" :groupID="props.groupID" />
			<fui-nav-bar :title="remark"></fui-nav-bar>
			<scroll-view
				id="messageScrollList"
				class="tui-message-list"
				scroll-y="true"
				:scroll-top="scrollTop"
				:scroll-into-view="`tui-${historyFirstMessageID}`"
				@scroll="handelScrollListScroll"
			>
				<p v-if="!isCompleted" class="message-more" @click="getHistoryMessageList">
					{{ TUITranslateService.t('TUIChat.查看更多') }}
				</p>
				<li v-for="(item, index) in messageList" :id="`tui-${item.ID}`" :key="item.vueForRenderKey" :class="'message-li ' + item.flow">
					<MessageTimestamp :currTime="item.time" :prevTime="index > 0 ? messageList[index - 1].time : 0" />
					<div class="message-item" @click="toggleID = ''">
						<MessageTip v-if="item.type === TYPES.MSG_GRP_TIP || isCreateGroupCustomMessage(item)" :content="item.getMessageContent()" />
						<div
							v-else-if="!item.isRevoked && !isPluginMessage(item)"
							:id="`msg-bubble-${item.ID}`"
							class="message-bubble-container"
							@longpress="handleToggleMessageItem($event, item, index, true)"
							@touchstart="handleH5LongPress($event, item, index, 'touchstart')"
							@touchend="handleH5LongPress($event, item, index, 'touchend')"
							@mouseover="handleH5LongPress($event, item, index, 'touchend')"
						>
							<MessageBubble
								:messageItem="deepCopy(item)"
								:content="item.getMessageContent()"
								:isAudioPlayed="audioPlayedMapping[item.ID]"
								:blinkMessageIDList="blinkMessageIDList"
								:isMultipleSelectMode="isMultipleSelectMode"
								:multipleSelectedMessageIDList="multipleSelectedMessageIDList"
								:index="index"
								@resendMessage="resendMessage(item)"
								@blinkMessage="blinkMessage"
								@scrollTo="scrollTo"
								@changeSelectMessageIDList="changeSelectMessageIDList"
								@setReadReceiptPanelVisible="setReadReceiptPanelVisible"
							>
								<MessageText v-if="item.type === TYPES.MSG_TEXT" :content="item.getMessageContent()" />
								<ProgressMessage v-else-if="item.type === TYPES.MSG_IMAGE" :content="item.getMessageContent()" :messageItem="deepCopy(item)">
									<MessageImage :content="item.getMessageContent()" :messageItem="item" @previewImage="handleImagePreview(index)" />
								</ProgressMessage>
								<ProgressMessage v-else-if="item.type === TYPES.MSG_VIDEO" :content="item.getMessageContent()" :messageItem="deepCopy(item)">
									<MessageVideo :content="item.getMessageContent()" :messageItem="item" />
								</ProgressMessage>
								<MessageAudio
									v-else-if="item.type === TYPES.MSG_AUDIO"
									:content="item.getMessageContent()"
									:messageItem="item"
									:broadcastNewAudioSrc="broadcastNewAudioSrc"
									@setAudioPlayed="setAudioPlayed"
									@getGlobalAudioContext="getGlobalAudioContext"
								/>
								<MessageRecord
									v-else-if="item.type === TYPES.MSG_MERGER"
									:renderData="item.payload"
									:messageItem="item"
									@assignMessageIDInUniapp="assignMessageIDInUniapp"
								/>
								<MessageFile v-else-if="item.type === TYPES.MSG_FILE" :content="item.getMessageContent()" />
								<MessageFace v-else-if="item.type === TYPES.MSG_FACE" :content="item.getMessageContent()" />
								<MessageLocation v-else-if="item.type === TYPES.MSG_LOCATION" :content="item.getMessageContent()" />
								<MessageCustom v-else-if="item.type === TYPES.MSG_CUSTOM" :content="item.getMessageContent()" :messageItem="item" />
							</MessageBubble>
						</div>
						<MessagePlugin
							v-else-if="!item.isRevoked && isPluginMessage(item)"
							:message="item"
							@resendMessage="resendMessage"
							@handleToggleMessageItem="handleToggleMessageItem"
							@handleH5LongPress="handleH5LongPress"
						/>
						<MessageRevoked v-else :isEdit="item.type === TYPES.MSG_TEXT" :messageItem="item" @messageEdit="handleEdit(item)" />
						<!-- message tool -->
						<MessageTool
							v-if="item.ID === toggleID"
							:class="{
								'message-tool': true,
								'message-tool-out': item.flow === 'out',
								'message-tool-in': item.flow === 'in'
							}"
							:messageItem="item"
							:isMultipleSelectMode="isMultipleSelectMode"
							@toggleMultipleSelectMode="() => emits('toggleMultipleSelectMode')"
						/>
					</div>
				</li>
			</scroll-view>
			<!-- scroll button -->
			<ScrollButton ref="scrollButtonInstanceRef" @scrollToLatestMessage="scrollToLatestMessage" />
			<Dialog
				v-if="reSendDialogShow"
				:show="reSendDialogShow"
				:isH5="!isPC"
				:center="true"
				:isHeaderShow="isPC"
				@submit="resendMessageConfirm()"
				@update:show="(e) => (reSendDialogShow = e)"
			>
				<p class="delDialog-title">
					{{ TUITranslateService.t('TUIChat.确认重发该消息？') }}
				</p>
			</Dialog>
			<!-- read receipt panel -->
			<ReadReceiptPanel v-if="isShowReadUserStatusPanel" :message="Object.assign({}, readStatusMessage)" @setReadReceiptPanelVisible="setReadReceiptPanelVisible" />
			<!-- simple message list -->
			<Drawer :visible="isShowSimpleMessageList" :overlayColor="'transparent'" :popDirection="'right'">
				<SimpleMessageList
					:style="{ height: '100%' }"
					:isMounted="isShowSimpleMessageList"
					:messageID="simpleMessageListRenderMessageID"
					@closeOverlay="isShowSimpleMessageList = false"
				/>
			</Drawer>
		</div>
	</div>
</template>

<script lang="ts" setup>
import { ref, watch, nextTick, onMounted, onUnmounted, getCurrentInstance } from '../../../adapter-vue';
import TUIChatEngine, { IMessageModel, TUIStore, StoreName, TUITranslateService, TUIChatService } from '@tencentcloud/chat-uikit-engine';
import { setInstanceMapping, getBoundingClientRect, getScrollInfo } from '@tencentcloud/universal-api';
// import { JoinGroupCard } from '@tencentcloud/call-uikit-wechat';
import Link from './link';
import SimpleMessageList from './message-elements/simple-message-list/index.vue';
import MessageGroupApplication from './message-group-application/index.vue';
import MessageText from './message-elements/message-text.vue';
import MessageImage from './message-elements/message-image.vue';
import MessageAudio from './message-elements/message-audio.vue';
import MessageRecord from './message-elements/message-record/index.vue';
import MessageFile from './message-elements/message-file.vue';
import MessageFace from './message-elements/message-face.vue';
import MessageCustom from './message-elements/message-custom.vue';
import MessageTip from './message-elements/message-tip.vue';
import MessageBubble from './message-elements/message-bubble.vue';
import MessageLocation from './message-elements/message-location.vue';
import MessageTimestamp from './message-elements/message-timestamp.vue';
import MessageVideo from './message-elements/message-video.vue';
import MessageTool from './message-tool/index.vue';
import MessageRevoked from './message-tool/message-revoked.vue';
import MessagePlugin from '../../../plugins/plugin-components/message-plugin.vue';
import ReadReceiptPanel from './read-receipt-panel/index.vue';
import ScrollButton from './scroll-button/index.vue';
import { isPluginMessage } from '../../../plugins/plugin-components/index';
import Dialog from '../../common/Dialog/index.vue';
import Drawer from '../../common/Drawer/index.vue';
import { Toast, TOAST_TYPE } from '../../common/Toast/index';
import ProgressMessage from '../../common/ProgressMessage/index.vue';
import { isCreateGroupCustomMessage } from '../utils/utils';
import { isEnabledMessageReadReceiptGlobal, deepCopy } from '../utils/utils';
import { throttle } from '../../../utils/lodash';
import { isPC, isH5, isMobile } from '../../../utils/env';
import chatStorage from '../utils/chatStorage';
import { IAudioContext } from '../../../interface';
import { inject } from 'vue';
interface IEmits {
	(e: 'closeInputToolBar'): void;
	(e: 'handleEditor', message: IMessageModel, type: string): void;
	(key: 'toggleMultipleSelectMode'): void;
}

interface IProps {
	isGroup: boolean;
	groupID: string;
	isNotInGroup: boolean;
	isMultipleSelectMode: boolean;
	selfAvatorUrl?: string;
	friendAvatorUrl?: string;
}
const { remark } = inject('userInfo');
const emits = defineEmits<IEmits>();
const props = withDefaults(defineProps<IProps>(), {
	isGroup: false,
	groupID: '',
	isNotInGroup: false,
	isMultipleSelectMode: false,
	selfAvatorUrl: '',
	friendAvatorUrl: ''
});

let selfAddValue = 0;
let observer: any = null;
let groupType: string | undefined;
const sentReceiptMessageID = new Set<string>();
const isOfficial = TUIStore.getData(StoreName.APP, 'isOfficial');
const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();

const messageList = ref<IMessageModel[]>();
const multipleSelectedMessageIDList = ref<string[]>([]);
const isCompleted = ref(false);
const currentConversationID = ref('');
const toggleID = ref('');
const scrollTop = ref(5000); // The initial number of messages is 15, and the maximum message height is 300.
const TYPES = ref(TUIChatEngine.TYPES);
const isLoadingMessage = ref(false);
const isLongpressing = ref(false);
const blinkMessageIDList = ref<string[]>([]);
const messageTarget = ref<IMessageModel>();
const scrollButtonInstanceRef = ref<InstanceType<typeof ScrollButton>>();
const historyFirstMessageID = ref<string>('');
const isShowSimpleMessageList = ref<boolean>(false);
const simpleMessageListRenderMessageID = ref<string>();
const audioPlayedMapping = ref<Record<string, boolean>>({});

// audio control
const broadcastNewAudioSrc = ref<string>('');

const readStatusMessage = ref<IMessageModel>();
const isShowReadUserStatusPanel = ref<boolean>(false);

// Resend Message Dialog
const reSendDialogShow = ref(false);
const resendMessageData = ref();

const scrollToBottom = () => {
	scrollTop.value += 300;
	// Solve the issue where swiping to the bottom for the first time after packaging Uniapp into an app has a delay,
	// which can be set to 300 ms.
	const timer = setTimeout(() => {
		scrollTop.value += 1;
		clearTimeout(timer);
	}, 300);
};

const onCurrentConversationIDUpdated = (conversationID: string) => {
	currentConversationID.value = conversationID;
	if (isEnabledMessageReadReceiptGlobal()) {
		const { groupProfile } = TUIStore.getConversationModel(conversationID) || {};
		groupType = groupProfile?.type;
	}

	if (Object.keys(audioPlayedMapping.value).length > 0) {
		// Synchronize storage about whether the audio has been played when converstaion switched
		chatStorage.setChatStorage('audioPlayedMapping', audioPlayedMapping.value);
	}
};

onMounted(() => {
	// Retrieve the information about whether the audio has been played from localStorage
	audioPlayedMapping.value = chatStorage.getChatStorage('audioPlayedMapping') || {};

	TUIStore.watch(StoreName.CHAT, {
		messageList: onMessageListUpdated,
		messageSource: onMessageSourceUpdated,
		isCompleted: onChatCompletedUpdated
	});

	TUIStore.watch(StoreName.CONV, {
		currentConversationID: onCurrentConversationIDUpdated
	});

	setInstanceMapping('messageList', thisInstance);

	uni.$on('scroll-to-bottom', scrollToLatestMessage);
});

onUnmounted(() => {
	TUIStore.unwatch(StoreName.CHAT, {
		messageList: onMessageListUpdated,
		isCompleted: onChatCompletedUpdated
	});

	TUIStore.unwatch(StoreName.CONV, {
		currentConversationID: onCurrentConversationIDUpdated
	});

	observer?.disconnect();
	observer = null;

	uni.$off('scroll-to-bottom');

	if (Object.keys(audioPlayedMapping.value).length > 0) {
		// Synchronize storage about whether the audio has been played when the component is unmounted
		chatStorage.setChatStorage('audioPlayedMapping', audioPlayedMapping.value);
	}
});

const handelScrollListScroll = throttle(
	function (e: Event) {
		scrollButtonInstanceRef.value?.judgeScrollOverOneScreen(e);
	},
	500,
	{ leading: true }
);

function getGlobalAudioContext(audioMap: Map<string, IAudioContext>, options?: { newAudioSrc: string }) {
	if (options?.newAudioSrc) {
		broadcastNewAudioSrc.value = options.newAudioSrc;
	}
}

async function onMessageListUpdated(list: IMessageModel[]) {
	observer?.disconnect();
	messageList.value = list
		.filter((message) => !message.isDeleted)
		.map((message) => {
			message.vueForRenderKey = `${message.ID}`;
			return message;
		});
	const newLastMessage = messageList.value?.[messageList.value?.length - 1];
	if (messageTarget.value) {
		// scroll to target message
		scrollAndBlinkMessage(messageTarget.value);
	} else if (!isLoadingMessage.value && !(scrollButtonInstanceRef.value?.isScrollButtonVisible && newLastMessage?.flow === 'in')) {
		// scroll to bottom
		nextTick(() => {
			scrollToBottom();
		});
	}
	if (isEnabledMessageReadReceiptGlobal()) {
		nextTick(() => bindIntersectionObserver());
	}
}

async function scrollToLatestMessage() {
	try {
		const { scrollHeight } = await getScrollInfo('#messageScrollList', 'messageList');
		if (scrollHeight) {
			scrollTop.value === scrollHeight ? (scrollTop.value = scrollHeight + 1) : (scrollTop.value = scrollHeight);
		} else {
			scrollToBottom();
		}
	} catch (error) {
		scrollToBottom();
	}
}

async function onMessageSourceUpdated(message: IMessageModel) {
	messageTarget.value = message;
	scrollAndBlinkMessage(messageTarget.value);
}

function scrollAndBlinkMessage(message: IMessageModel) {
	if (messageList.value?.some((messageListItem) => messageListItem?.ID === message?.ID)) {
		nextTick(async () => {
			await scrollToTargetMessage(message);
			await blinkMessage(message?.ID);
			messageTarget.value = undefined;
		});
	}
}

function onChatCompletedUpdated(flag: boolean) {
	isCompleted.value = flag;
}

const getHistoryMessageList = () => {
	isLoadingMessage.value = true;
	const currentFirstMessageID = messageList.value?.[0]?.ID || '';
	TUIChatService.getMessageList().then(() => {
		nextTick(() => {
			historyFirstMessageID.value = currentFirstMessageID;
			const timer = setTimeout(() => {
				historyFirstMessageID.value = '';
				isLoadingMessage.value = false;
				clearTimeout(timer);
			}, 500);
		});
	});
};

const openComplaintLink = () => {};

// toggle message
const handleToggleMessageItem = (e: any, message: IMessageModel, index: number, isLongpress = false) => {
	if (props.isMultipleSelectMode || props.isNotInGroup) {
		return;
	}
	if (isLongpress) {
		isLongpressing.value = true;
	}
	toggleID.value = message.ID;
};

// h5 long press
let timer: number;
const handleH5LongPress = (e: any, message: IMessageModel, index: number, type: string) => {
	if (props.isMultipleSelectMode || props.isNotInGroup) {
		return;
	}
	if (!isH5) return;
	function longPressHandler() {
		clearTimeout(timer);
		handleToggleMessageItem(e, message, index, true);
	}
	function touchStartHandler() {
		timer = setTimeout(longPressHandler, 500);
	}
	function touchEndHandler() {
		clearTimeout(timer);
	}
	switch (type) {
		case 'touchstart':
			touchStartHandler();
			break;
		case 'touchend':
			touchEndHandler();
			setTimeout(() => {
				isLongpressing.value = false;
			}, 200);
			break;
	}
};

// reedit message
const handleEdit = (message: IMessageModel) => {
	emits('handleEditor', message, 'reedit');
};

const resendMessage = (message: IMessageModel) => {
	reSendDialogShow.value = true;
	resendMessageData.value = message;
};

const handleImagePreview = (index: number) => {
	if (!messageList.value) {
		return;
	}
	const imageMessageIndex: number[] = [];
	const imageMessageList: IMessageModel[] = messageList.value.filter((item, index) => {
		if (!item.isRevoked && !item.hasRiskContent && item.type === TYPES.value.MSG_IMAGE) {
			imageMessageIndex.push(index);
			return true;
		}
		return false;
	});
	uni.previewImage({
		current: imageMessageIndex.indexOf(index),
		urls: imageMessageList.map((message) => message.payload.imageInfoArray?.[2].url),
		// #ifdef APP-PLUS
		indicator: 'number'
		// #endif
	});
};

const resendMessageConfirm = () => {
	reSendDialogShow.value = !reSendDialogShow.value;
	const messageModel = resendMessageData.value;
	messageModel.resendMessage();
};

function blinkMessage(messageID: string): Promise<void> {
	return new Promise((resolve) => {
		const index = blinkMessageIDList.value.indexOf(messageID);
		if (index < 0) {
			blinkMessageIDList.value.push(messageID);
			const timer = setTimeout(() => {
				blinkMessageIDList.value.splice(blinkMessageIDList.value.indexOf(messageID), 1);
				clearTimeout(timer);
				resolve();
			}, 3000);
		}
	});
}

function scrollTo(scrollHeight: number) {
	scrollTop.value = scrollHeight;
}

async function bindIntersectionObserver() {
	if (!messageList.value || messageList.value.length === 0) {
		return;
	}

	if (groupType === TYPES.value.GRP_AVCHATROOM || groupType === TYPES.value.GRP_COMMUNITY) {
		// AVCHATROOM and COMMUNITY chats do not monitor read receipts for messages.
		return;
	}

	observer?.disconnect();
	observer = uni
		.createIntersectionObserver(thisInstance, {
			threshold: [0.7],
			observeAll: true
			// In Uni-app, the `safetip` is also included, so a negative margin is needed to exclude it.
		})
		.relativeTo('#messageScrollList', { top: -70 });

	observer?.observe('.message-li.in .message-bubble-container', (res: any) => {
		if (sentReceiptMessageID.has(res.id)) {
			return;
		}
		const matchingMessage = messageList.value.find((message: IMessageModel) => {
			return res.id.indexOf(message.ID) > -1;
		});
		if (matchingMessage && matchingMessage.needReadReceipt && matchingMessage.flow === 'in' && !matchingMessage.readReceiptInfo?.isPeerRead) {
			TUIChatService.sendMessageReadReceipt([matchingMessage]);
			sentReceiptMessageID.add(res.id);
		}
	});
}

function setReadReceiptPanelVisible(visible: boolean, message?: IMessageModel) {
	if (visible && props.isNotInGroup) {
		return;
	}
	if (!visible) {
		readStatusMessage.value = undefined;
	} else {
		readStatusMessage.value = message;
	}
	isShowReadUserStatusPanel.value = visible;
}

async function scrollToTargetMessage(message: IMessageModel) {
	const targetMessageID = message.ID;
	const isTargetMessageInScreen = messageList.value && messageList.value.some((msg) => msg.ID === targetMessageID);
	if (targetMessageID && isTargetMessageInScreen) {
		const timer = setTimeout(async () => {
			try {
				const scrollViewRect = await getBoundingClientRect('#messageScrollList', 'messageList');
				const originalMessageRect = await getBoundingClientRect('#tui-' + targetMessageID, 'messageList');
				const { scrollTop } = await getScrollInfo('#messageScrollList', 'messageList');
				const finalScrollTop = originalMessageRect.top + scrollTop - scrollViewRect.top - (selfAddValue++ % 2);
				scrollTo(finalScrollTop);
				clearTimeout(timer);
			} catch (error) {
				// todo
			}
		}, 500);
	} else {
		Toast({
			message: TUITranslateService.t('TUIChat.无法定位到原消息'),
			type: TOAST_TYPE.WARNING
		});
	}
}

function onMessageListBackgroundClick() {
	emits('closeInputToolBar');
}

watch(
	() => props.isMultipleSelectMode,
	(newValue) => {
		if (!newValue) {
			changeSelectMessageIDList({
				type: 'clearAll',
				messageID: ''
			});
		}
	}
);

function changeSelectMessageIDList({ type, messageID }: { type: 'add' | 'remove' | 'clearAll'; messageID: string }) {
	// TODO need to delete this
	if (type === 'clearAll') {
		multipleSelectedMessageIDList.value = [];
	} else if (type === 'add' && !multipleSelectedMessageIDList.value.includes(messageID)) {
		multipleSelectedMessageIDList.value.push(messageID);
	} else if (type === 'remove') {
		multipleSelectedMessageIDList.value = multipleSelectedMessageIDList.value.filter((id) => id !== messageID);
	}
}

function mergeForwardMessage() {
	TUIStore.update(StoreName.CUSTOM, 'multipleForwardMessageID', {
		isMergeForward: true,
		messageIDList: multipleSelectedMessageIDList.value
	});
}

function oneByOneForwardMessage() {
	TUIStore.update(StoreName.CUSTOM, 'multipleForwardMessageID', {
		isMergeForward: false,
		messageIDList: multipleSelectedMessageIDList.value
	});
}

function assignMessageIDInUniapp(messageID: string) {
	simpleMessageListRenderMessageID.value = messageID;
	isShowSimpleMessageList.value = true;
}

function setAudioPlayed(messageID: string) {
	audioPlayedMapping.value[messageID] = true;
}

defineExpose({
	oneByOneForwardMessage,
	mergeForwardMessage,
	scrollToLatestMessage
});
</script>
<style lang="scss" scoped src="./style/index.scss"></style>
